﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.AspNet.Identity;
using Microsoft.AspNet.Identity.EntityFramework;
using Microsoft.AspNet.Identity.Owin;
using Microsoft.Owin.Security;
using Microsoft.Owin.Security.Cookies;
using Microsoft.Owin.Security.OAuth;
using ezCloud.Api.Models;
using ezCloud.Data;
//using System.Web.Mvc;
using System.Configuration;
//using System.Web.Http.ModelBinding;
//using System.Web.Http.ModelBinding;

namespace ezCloud.Api.Providers
{
    public class ApplicationOAuthProvider : OAuthAuthorizationServerProvider
    {
        private readonly string _publicClientId;

        public ApplicationOAuthProvider(string publicClientId)
        {
            if (publicClientId == null)
            {
                throw new ArgumentNullException("publicClientId");
            }

            _publicClientId = publicClientId;
        }

        public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
        {
            //context.OwinContext.Response.Headers.Add("Access-Control-Allow-Origin", new[] { "*" });
            var userManager = context.OwinContext.GetUserManager<ApplicationUserManager>();
            var user = await userManager.FindByNameAsync(context.UserName);
            if (user != null)
            {

                var validCredentials = await userManager.FindAsync(context.UserName, context.Password);
                /*if (await userManager.IsLockedOutAsync(user.Id))
                {
                    // context.SetError("", string.Format("Tài khoản của bạn bị khóa trong vòng {0} phút vì đăng nhập sai 3 lần.", ConfigurationManager.AppSettings["DefaultAccountLockoutTimeSpan"].ToString()));
                    context.SetError("login_error", string.Format("YOUR_ACCOUNT_HAS_BEEN_LOCKED OUT_FOR_{0}_MINUTES_DUE_TO_MULTIPLE_FAILED_LOGIN_ATTEMPTS.", ConfigurationManager.AppSettings["DefaultAccountLockoutTimeSpan"].ToString()));
                }
                else if (await userManager.GetLockoutEnabledAsync(user.Id) && validCredentials == null)
                {
                    await userManager.AccessFailedAsync(user.Id);
                    string message;
                    if (await userManager.IsLockedOutAsync(user.Id))
                    {
                        message = string.Format("YOUR_ACCOUNT_HAS_BEEN_LOCKED OUT_FOR_{0}_MINUTES_DUE_TO_MULTIPLE_FAILED_LOGIN_ATTEMPTS.", ConfigurationManager.AppSettings["DefaultAccountLockoutTimeSpan"].ToString());
                    }
                    else
                    {
                        int accessFailedCount = await userManager.GetAccessFailedCountAsync(user.Id);

                        int attemptsLeft =
                            Convert.ToInt32(
                                ConfigurationManager.AppSettings["MaxFailedAccessAttemptsBeforeLockout"].ToString()) -
                            accessFailedCount;

                        message = string.Format(
                            "INVALID_CREDENTIALS_YOU_HAVE_{0}_MORE_ATTEMPT(S)_BEFORE_YOUR_ACCOUNT_GETS_LOCKED_OUT.", attemptsLeft);

                    }
                    context.SetError("login_error", message);
                }
                else */if (validCredentials == null)
                {
                    context.SetError("login_error", "INVALID_CREDENTIALS_PLEASE_TRY_AGAIN.");
                    return;
                }
                else
                {
                    if (!validCredentials.EmailConfirmed)
                    {
                        context.SetError("login_error", "YOUR_EMAIL_ADDRESS_HAS_NOT_BEEN_CONFIRMED_YET");
                        return;
                    }
                    else
                    {
                        var data = await context.Request.ReadFormAsync();
                        var isAdmin = false;
                        
                        await userManager.ResetAccessFailedCountAsync(validCredentials.Id);
                        ClaimsIdentity oAuthIdentity = await user.GenerateUserIdentityAsync(userManager,
                        OAuthDefaults.AuthenticationType);
                        ClaimsIdentity cookiesIdentity = await user.GenerateUserIdentityAsync(userManager,
                            CookieAuthenticationDefaults.AuthenticationType);
                        // var identity = new ClaimsIdentity(context.Options.AuthenticationType);
                        //identity.AddClaim(new Claim(ClaimTypes.Role, "ROLE_HOTEL_STAFF"));
                        var roles = oAuthIdentity.Claims
                       .Where(c => c.Type == ClaimTypes.Role)
                       .Select(c => c.Value)
                       .ToList();
                        var isOwner = false;
                        var roleString = "";
                        foreach (var role in roles)
                        {
                            if (role == "ROLE_HOTEL_OWNER")
                                isOwner = true;
                            roleString += role + ",";
                        }
                        if (roleString.Length > 0)
                            roleString = roleString.Substring(0, roleString.Length - 1);
                        var userName = validCredentials.UserName;
                        if (!isOwner)
                        {
                            userName = userName.Substring(0, userName.LastIndexOf("@"));

                        }
                        AuthenticationProperties properties = CreateProperties(userName);
                        properties.Dictionary.Add("Roles", roleString);
                        var dbContext = new ezCloudDataContext();
                        if (data != null)
                        {
                            var strHotelId = data["hotelId"];
                            if (strHotelId != null)
                            {
                                int hotelId = Int32.Parse(strHotelId);
                                var adminAccounts = ConfigurationSettings.AppSettings["adminAccounts"];
                                if (adminAccounts != null)
                                {
                                    if (adminAccounts.IndexOf(context.UserName.ToLower()) >= 0)
                                    {
                                        //admin account
                                        isAdmin = true;
                                        var owner = dbContext.AspNetUsers.FirstOrDefault(h => h.UserName.ToLower() == context.UserName.ToLower());
                                        if (owner != null)
                                        {
                                            Common.UserUtitily.removeUserProfileCache(context.UserName);
                                            owner.DefaultHotelId = hotelId;
                                            dbContext.SubmitChanges();
                                        }
                                    }
                                }
                            }
                        }
                        //if (isOwner || isAdmin)
                        if (isAdmin)
                        {
                            properties.Dictionary.Add("Email", user.Email);
                            var hotelOwner = dbContext.HotelOwners.Where(owner => owner.UserId == user.Id && owner.IsActive==true).FirstOrDefault();
                            if (hotelOwner != null)
                            {
                                properties.Dictionary.Add("Fullname", hotelOwner.FullName);
                                int countHotelIsActive = dbContext.Hotels.Where(h => h.HotelOwnerId == user.Id && h.IsActive == true).Count();
                                if (countHotelIsActive == 0)
                                {
                                    context.SetError("login_error", "HOTEL_HAS_BLOCKED");
                                    return;
                                }
                            }
                            else
                            {
                                context.SetError("login_error", "HOTEL_HAS_BLOCKED");
                                return;
                            }          
                                              
                        }
                        else
                        {

                            var staff = dbContext.Staffs.Where(st => st.UserId == user.Id).FirstOrDefault();
                            if (staff != null)
                            {                               
                                Hotels hotel = dbContext.Hotels.Where(h => h.HotelId == staff.HotelId && h.IsActive).FirstOrDefault();
                                //HotelOwners hotelOwner = dbContext.HotelOwners.Where(owner => owner.UserId == user.Id && owner.IsActive == true).FirstOrDefault();
                             
                                if (hotel == null)
                                {
                                    context.SetError("login_error", "HOTEL_HAS_BLOCKED");
                                    return;
                                }
                                else
                                {
                                    properties.Dictionary.Add("Email", staff.Email);
                                    properties.Dictionary.Add("Fullname", staff.StaffName);
                                    if (!staff.IsActive)
                                    {
                                        context.SetError("login_error", "ACCOUNT_HAS_BEEN_BANNED");
                                        return;
                                    }
                                }
                                

                            }
                            else
                            {
                                
                                HotelOwners hotelOwner = dbContext.HotelOwners.Where(owner => owner.UserId == user.Id && owner.IsActive == true).FirstOrDefault();
                                if (hotelOwner == null)
                                {
                                   
                                    context.SetError("login_error", "HOTEL_HAS_BLOCKED");
                                    return;
                                   
                                }
                                else
                                {
                                    var countHotel = dbContext.Hotels.Where(h => h.HotelOwnerId == hotelOwner.UserId).Count();
                                    if (countHotel>0)
                                    {
                                        Hotels hotel = dbContext.Hotels.Where(h => h.HotelOwnerId == hotelOwner.UserId && h.IsActive).FirstOrDefault();
                                        if (hotel == null)
                                        {
                                            context.SetError("login_error", "HOTEL_HAS_BLOCKED");
                                            return;
                                        }
                                        else
                                        {
                                            //properties.Dictionary.Add("Email", hotelOwner.);
                                            properties.Dictionary.Add("Fullname", hotelOwner.FullName);
                                            //if (!(bool)hotelOwner.IsActive)
                                            //{
                                            //    context.SetError("login_error", "ACCOUNT_HAS_BEEN_BANNED");
                                            //    return;
                                            //}
                                        }
                                    }
                                    else
                                    {
                                        properties.Dictionary.Add("Fullname", hotelOwner.FullName);
                                    }
                                    
                                   
                                    
                                }
                            }


                        }

                        properties.Dictionary.Add("as:client_id", context.ClientId);
                        AuthenticationTicket ticket = new AuthenticationTicket(oAuthIdentity, properties);
                        context.Validated(ticket);
                        context.Request.Context.Authentication.SignIn(cookiesIdentity);

                    }
                }
            }
            else {
                context.SetError("login_error", "YOUR_EMAIL_NOT_EXIST");
                return;
            }
            return;
        }

        public override Task AuthorizationEndpointResponse(OAuthAuthorizationEndpointResponseContext context)
        {
            var refreshToken = context.OwinContext.Authentication.AuthenticationResponseGrant.Properties.Dictionary.ContainsKey("refresh_token")? context.OwinContext.Authentication.AuthenticationResponseGrant.Properties.Dictionary["refresh_token"]:null;
            
            if (!string.IsNullOrEmpty(refreshToken))
            {
                var userSessionManager = new Common.UserSessionManager();
                userSessionManager.CreateUserSession(context.Identity.GetUserName(), context.AccessToken);
                context.AdditionalResponseParameters.Add("refresh_token", refreshToken);
                context.AdditionalResponseParameters.Add(".expires", DateTime.UtcNow + context.Options.AccessTokenExpireTimeSpan);
                var roles = context.Identity.Claims
                       .Where(c => c.Type == ClaimTypes.Role)
                       .Select(c => c.Value)
                       .ToList();
                var isOwner = false;
                var roleString = "";
                foreach (var role in roles)
                {
                    if (role == "ROLE_HOTEL_OWNER")
                        isOwner = true;
                    roleString += role + ",";
                }
                if (roleString.Length > 0)
                    roleString = roleString.Substring(0, roleString.Length - 1);
                var userName = context.Identity.GetUserName();
                if (!isOwner)
                {
                    userName = userName.Substring(0, userName.LastIndexOf("@"));
                }
                context.AdditionalResponseParameters.Add("userName", userName);
                context.AdditionalResponseParameters.Add("Roles", roleString);
                var dbContext = new ezCloudDataContext();
                var user = dbContext.AspNetUsers.Where(u => u.Id == context.Identity.GetUserId()).First();
                if (isOwner)
                {
                    context.AdditionalResponseParameters.Add("Email", user.Email);
                    var hotelOwner = dbContext.HotelOwners.Where(owner => owner.UserId == user.Id).FirstOrDefault();
                    if (hotelOwner != null)
                    {
                        context.AdditionalResponseParameters.Add("Fullname", hotelOwner.FullName);
                    }
                }
                else
                {

                    var staff = dbContext.Staffs.Where(st => st.UserId == user.Id).FirstOrDefault();
                    if (staff != null)
                    {
                        context.AdditionalResponseParameters.Add("Email", staff.Email);
                        context.AdditionalResponseParameters.Add("Fullname", staff.StaffName);
                    }
                }

                context.AdditionalResponseParameters.Add("as:client_id", "ezCloud");
            } else
            {
                var email = context.Identity.Claims.FirstOrDefault(c => c.Type == ClaimTypes.Email);
                if (email != null)
                    context.AdditionalResponseParameters.Add("Email", email.Value);
                var fullname = context.Identity.Claims.FirstOrDefault(c => c.Type == ClaimTypes.Name);
                if (fullname != null)
                    context.AdditionalResponseParameters.Add("Fullname", fullname.Value);

            }
            return base.AuthorizationEndpointResponse(context);
        }
        public override async Task GrantRefreshToken(OAuthGrantRefreshTokenContext context)
        {
            var originalClient = context.Ticket.Properties.Dictionary["as:client_id"];
            var currentClient = context.OwinContext.Get<string>("as:client_id");

            if (originalClient != currentClient)
            {
                context.Rejected();
                return;
            }

            var newId = new ClaimsIdentity(context.Ticket.Identity);
            var newTicket = new AuthenticationTicket(newId, context.Ticket.Properties);
            context.Validated(newTicket);
        }


        public override Task TokenEndpoint(OAuthTokenEndpointContext context)
        {
            foreach (KeyValuePair<string, string> property in context.Properties.Dictionary)
            {
                context.AdditionalResponseParameters.Add(property.Key, property.Value);
            }

            return Task.FromResult<object>(null);
        }

        public override Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context)
        {
            // Resource owner password credentials does not provide a client ID.
            /* if (context.ClientId == null)
             {
                 context.Validated();
             }

             return Task.FromResult<object>(null);*/
            string id, secret;
            if (context.TryGetFormCredentials(out id, out secret))
            {
                if (secret == "secret")
                {
                    // need to make the client_id available for later security checks
                    context.OwinContext.Set<string>("as:client_id", id);
                    context.Validated();
                }
            }
            return Task.FromResult<object>(null);
        }

        public override Task ValidateClientRedirectUri(OAuthValidateClientRedirectUriContext context)
        {
            if (context.ClientId == _publicClientId)
            {
                Uri expectedRootUri = new Uri(context.Request.Uri, "/");

                if (context.RedirectUri.Contains("authcomplete.html") || context.RedirectUri.StartsWith(expectedRootUri.AbsoluteUri) || (context.RedirectUri.Contains("http://localhost") && expectedRootUri.AbsoluteUri.Contains("http://localhost")))
                {
                    context.Validated();
                }
            }

            return Task.FromResult<object>(null);
        }

        public static AuthenticationProperties CreateProperties(string userName)
        {
            IDictionary<string, string> data = new Dictionary<string, string>
            {
                { "userName", userName }
            };
            return new AuthenticationProperties(data);
        }

    }
}